package com.qianjing.framework.security.auth;

import com.qianjing.framework.security.LoginTicket;
import com.qianjing.framework.security.LoginUser;
import com.qianjing.framework.security.service.OAuthUserDetailsService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;

/**
 * @Classname OAuthAuthenticationProvider
 * @Description 单点统一认证用户信息
 * @Created by zzy 2020年8月28日 下午3:55:54
 * @Version V1.0
 */
public class OAuthAuthenticationProvider implements AuthenticationProvider {
    protected final Log logger = LogFactory.getLog(getClass());
    private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    private final OAuthUserDetailsService userDetailsService;

    public OAuthAuthenticationProvider(OAuthUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LoginTicket loginTicket = (LoginTicket) authentication.getPrincipal();
        LoginUser user = userDetailsService.loadUserByUsername(loginTicket.getUserName());
        if (user != null) {
            user = userDetailsService.updateSsoUser(user.getUser(), loginTicket);
        } else {
            user = userDetailsService.createSsoUser(loginTicket);
        }
        preAuthenticationChecks.check(user);
        postAuthenticationChecks.check(user);
        return createSuccessAuthentication(authentication, user);
    }

    private Authentication createSuccessAuthentication(Authentication authentication, UserDetails user) {
        OAuthAuthenticationToken token = new OAuthAuthenticationToken(user, authentication.getCredentials(),
                user.getAuthorities());
        token.setDetails(authentication.getDetails());
        return token;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(OAuthAuthenticationToken.class);
    }

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                logger.debug("User account is locked");
                throw new LockedException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.locked",
                        "User account is locked"));
            }
            if (!user.isEnabled()) {
                logger.debug("User account is disabled");
                throw new DisabledException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.disabled",
                        "User is disabled"));
            }
            if (!user.isAccountNonExpired()) {
                logger.debug("User account is expired");
                throw new AccountExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.expired",
                        "User account has expired"));
            }
        }
    }

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                logger.debug("User account credentials have expired");
                throw new CredentialsExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
                        "User credentials have expired"));
            }
        }
    }

    public void setMessages(MessageSourceAccessor messages) {
        this.messages = messages;
    }

    public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
        this.preAuthenticationChecks = preAuthenticationChecks;
    }

    public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
        this.postAuthenticationChecks = postAuthenticationChecks;
    }
}
